﻿Ext.util.Scroller = Ext.extend(Ext.util.Draggable, {
    
    baseCls: '',
    draggingCls: '',
    direction: 'both',
    constrain: 'parent',
    outOfBoundRestrictFactor: 0.5,
    acceleration: 20,//减速的加速度
    autoAdjustFps: false,
    friction: 0.5,//摩擦系数
    startMomentumResetTime: 350,//滚动重置时间（即如果拖拽时间大与350毫秒，着拖拽开始点加350毫秒，保证拖拽从开始到结束的时间间隔小于350毫秒）
    springTension: 0.3,//弹簧张力
    minVelocityForAnimation: 1,
    bounces: true,//弹簧标志，true相当与'both'，另外还有'horizontal'和'vertical'两个可选值
    momentum: true,//是否启用滚动冲力
    cancelRevert: true,
    threshold: 5,
	
    constructor: function(el, config) {
        el = Ext.get(el);//滚动的节点
		//获取滚动节点对应的滚动类
        var scroller = Ext.ScrollManager.get(el.id);
		//如果该节点之前已经有了对应的滚动类
        if (scroller) {
            return Ext.apply(scroller, config);//返回该滚动类，滚动类的配置属性换成配置对象的配置属性
        }

		//调用父类构造函数
        Ext.util.Scroller.superclass.constructor.apply(this, arguments);

		//注册事件
        this.addEvents(
            'scrollstart',
            'scroll',
            'scrollend',
            'bouncestart',
            'bounceend'
        );

        this.on({
            dragstart: this.onDragStart,//拖拽开始时触发
            offsetchange: this.onOffsetChange,//拖拽节点坐标改变时触发
            scope: this
        });

        Ext.ScrollManager.register(this);

        this.el.addCls('x-scroller');//滚动节点添加类
        this.container.addCls('x-scroller-parent');//滚动范围节点添加类

		//如果设置了弹簧属性
        if (this.bounces !== false) {
            var both = this.bounces === 'both' || this.bounces === true,
                horizontal = both || this.bounces === 'horizontal',
                vertical = both || this.bounces === 'vertical';

			//弹簧属性对象
            this.bounces = {
                x: horizontal,//横向滚动是否具有弹性
                y: vertical//纵向滚动是否具有弹性
            };
        }

        this.theta = Math.log(1 - (this.friction / 10));
		
        this.bouncingVelocityFactor = this.springTension * Math.E;//Math.E:返回算术常量 e，即自然对数的底数（约等于2.718）。
		
        this.bouncingTimeFactor = ((1 / this.springTension) * this.acceleration);

		//如果没有设置减速动画对象
        if (!this.decelerationAnimation) {
            this.decelerationAnimation = {};
        }

		//如果没有设置弹性动画对象
        if (!this.bouncingAnimation) {
            this.bouncingAnimation = {};
        }

		//给相关对象设置x坐标和y坐标相关的属性和方法
        ['x', 'y'].forEach(function(a) {
			//减速动画对象的对应坐标属性
            if (!this.decelerationAnimation[a]) {
				//减速动画对象单坐标对象
                this.decelerationAnimation[a] = new Ext.util.Scroller.Animation.Deceleration({
                    acceleration: this.acceleration,//加速度
                    theta: this.theta
                });
            }
			//弹性动画对象的对应坐标属性
            if (!this.bouncingAnimation[a]) {
				//弹性动画对象单坐标对象
                this.bouncingAnimation[a] = new Ext.util.Scroller.Animation.Bouncing({
                    acceleration: this.acceleration,//加速度
                    springTension: this.springTension//弹性系数
                });
            }
        }, this);

        return this;
    },

    
    updateBoundary: function(animate) {
        Ext.util.Scroller.superclass.updateBoundary.apply(this, arguments);

        this.snapToBoundary(animate);

        return this;
    },

    //拖拽节点位置改变时触发
    onOffsetChange: function(scroller, offset) {
        this.fireEvent('scroll', scroller, {//触发'scroll'事件
            x: -offset.x,
            y: -offset.y
        });
    },

    //拖拽节点'touchstart'事件响应函数
    onTouchStart: function(e) {
        Ext.util.Scroller.superclass.onTouchStart.apply(this, arguments);//父类无实现

        this.stopMomentumAnimation();//结束冲力动画
    },

    //拖拽开始时触发
    onDragStart: function(e) {
        this.fireEvent('scrollstart', this, e);//触发'scrollstart'事件
    },

    //设置开始时间
    setStartTime: function(e) {
        this.startTime = e.time;//开始时间
        this.originalStartTime = (e.event.originalTimeStamp) ? e.event.originalTimeStamp : e.time;//开始时间撮
    },

    //'taphold'或'dragstart'事件
    onStart: function(e) {
		//调用父类方法，主要用于停止拖拽动画，记录开始坐标
        if (Ext.util.Scroller.superclass.onStart.apply(this, arguments) !== true)
            return;

        this.setStartTime(e);//设置开始时间
        this.lastEventTime = e.time;//前一时间也为开始时间
        this.startTimeOffset = this.offset.copy();//开始的偏移量
        this.isScrolling = true;//开始滚动

        this.momentumAnimationFramesHandled = 0;
    },

    //拖拽过程响应函数
    onDrag: function(e) {
		//调用父类同名方法，用于在指定范围内拖拽节点
        if (Ext.util.Scroller.superclass.onDrag.apply(this, arguments) !== true)
            return;

        this.lastEventTime = e.time;//先前时刻变为当前时刻

		//如果拖拽时间超过了滚动重置时间
        if (this.lastEventTime - this.startTime > this.startMomentumResetTime) {
            this.setStartTime(e);//当前时间直接设为开始时间
            this.startTimeOffset = this.offset.copy();//开始点的偏移对象设为当前偏移量
        }
    },

    //拖拽结束时的响应函数
    onDragEnd: function(e) {
		//调用父类同名方法，主要重设拖拽状态
        if (Ext.util.Scroller.superclass.onDragEnd.apply(this, arguments) !== true)
            return;

		//开始冲力动画
        if (!this.startMomentumAnimation(e)) {
            this.fireScrollEndEvent();
        }
    },

    //转屏触发
    onOrientationChange: function() {
        Ext.util.Scroller.superclass.onOrientationChange.apply(this, arguments);

        this.snapToBoundary();//设置指定范围内的新坐标
    },

    //滚动事件结束，设置各标志值，触发事件
    fireScrollEndEvent: function() {
        this.isScrolling = false;
        this.isMomentumAnimating = false;
        this.snapToBoundary();//设置指定范围内的新坐标
        this.fireEvent('scrollend', this, this.getOffset());//触发'scrollend'事件

        this.snapToSlot();
    },


    //获取动画剩余量频率
    getLastActualFps: function() {
        var duration = (this.momentumAnimationEndTime - this.momentumAnimationStartTime - this.momentumAnimationProcessingTime) / 1000;//动画剩余时间
        return this.momentumAnimationFramesHandled / duration;//动画剩余频率
    },

    
    scrollTo: function(pos, animate) {
        this.stopMomentumAnimation();

        var newOffset = this.offsetBoundary.restrict(new Ext.util.Offset(-pos.x, -pos.y));

        this.setOffset(newOffset, animate);

        return this;
    },

    
    scrollBy: function(offset, animate) {
        this.stopMomentumAnimation();

        var newOffset = this.offset.copy();
        newOffset.x += offset.x;
        newOffset.y += offset.y;

        this.setOffset(newOffset, animate);

        return this;
    },

    
    setSnap: function(snap) {
        this.snap = snap;
    },

    //设置指定范围内的新坐标
    snapToBoundary: function(animate) {
        var offset = this.offsetBoundary.restrict(this.offset);//指定范围内的偏移量偏移量
        this.setOffset(offset, animate);//设置新坐标

        return this;
    },

    snapToSlot: function() {
        var offset = this.offsetBoundary.restrict(this.offset);//在范围之内的偏移量
        offset.round();//偏移量四舍五入值

		//如果要对齐
        if (this.snap) {
            if (this.snap === true) {
                this.snap = {
                    x: 50,
                    y: 50
                };
            }
            else if (Ext.isNumber(this.snap)) {
                this.snap = {
                    x: this.snap,
                    y: this.snap
                };
            }
            if (this.snap.y) {
                offset.y = Math.round(offset.y / this.snap.y) * this.snap.y;
            }
            if (this.snap.x) {
                offset.x = Math.round(offset.x / this.snap.x) * this.snap.x;
            }

            if (!this.offset.equals(offset)) {
                this.scrollTo({x: -offset.x, y: -offset.y}, this.snapDuration);
            }
        }
    },

    //开始冲力动画
    startMomentumAnimation: function(e) {
        var me = this,
            originalTime = (e.event.originalTimeStamp) ? e.event.originalTimeStamp : e.time,//原始时间撮
            duration = Math.max(40, originalTime - this.originalStartTime);//动画开始时间与点击开始时间的时间间隔

        this.fireEvent('beforemomentumanimationstart');//触发动画开始前事件

        if (//如果没有启用滚动冲力，或动画开始时间与点击开始时间的时间间隔大于滚动重置时间且偏移坐标轴区域范围之内
            (!this.momentum || !(duration <= this.startMomentumResetTime)) &&
            !this.offsetBoundary.isOutOfBound(this.offset)//
        ) {
            return false;//直接返回
        }

        
        var minVelocity = this.minVelocityForAnimation,//保持动画的最低速度（默认1px per second）
            currentVelocity,
            currentOffset = this.offset.copy(),//偏移坐标
            restrictedOffset,
            acceleration = (duration / this.acceleration);//拖拽时间与加速度的比

        this.isBouncing = {x: false, y: false};//弹簧对象
        this.isDecelerating = {x: false, y: false};//减速对象
        this.momentumAnimationStartTime = e.time;//冲力动画开始时间
        this.momentumAnimationProcessingTime = 0;//冲力动画经过的时间
        this.bouncingData = {x: null, y: null};//弹簧数据

        //当前速度对象
        this.momentumAnimationStartVelocity = {
            x: (this.offset.x - this.startTimeOffset.x) / acceleration,//横坐标速度
            y: (this.offset.y - this.startTimeOffset.y) / acceleration//纵坐标速度
        };

        this.momentumAnimationStartOffset = currentOffset;//开始偏移坐标为当前偏移坐标

		//遍历x，y以统一坐标处理
        ['x', 'y'].forEach(function(axis) {

            this.isDecelerating[axis] = (Math.abs(this.momentumAnimationStartVelocity[axis]) > minVelocity);//当前方向的速度是否大于最低速度

			//如果当前方向上设了弹簧属性
            if (this.bounces && this.bounces[axis]) {
                restrictedOffset = this.offsetBoundary.restrict(axis, currentOffset[axis]);//要移动的偏移坐标

				//如果要移动的偏移坐标不等于当前偏移坐标
                if (restrictedOffset !== currentOffset[axis]) {
                    currentVelocity = (currentOffset[axis] - restrictedOffset) * this.bouncingVelocityFactor;

                    this.bouncingData[axis] = {
                        axis: axis,
                        offset: restrictedOffset,
                        time: this.momentumAnimationStartTime,
                        velocity: currentVelocity
                    };

                    this.isBouncing[axis] = true;
                    this.isDecelerating[axis] = false;

                    this.fireEvent('bouncestart', this, this.bouncingData[axis]);//弹性事件开始

					//当前方向上的弹性动画对象设置属性
                    this.bouncingAnimation[axis].set({
                        startTime: this.bouncingData[axis].time - this.bouncingTimeFactor,
                        startOffset: this.bouncingData[axis].offset,
                        startVelocity: this.bouncingData[axis].velocity
                    });
                }
            }

			//当前方向上的减速动画对象设置属性
            if (this.isDecelerating[axis]) {
                this.decelerationAnimation[axis].set({
                    startVelocity: this.momentumAnimationStartVelocity[axis],//当前方向上的减速动画对象
                    startOffset: this.momentumAnimationStartOffset[axis],//开始时刻的偏移坐标
                    startTime: this.momentumAnimationStartTime//开始时刻的时间
                });
            }
        }, this);

		//如果有方向需要继续减速或做弹性动作
        if (this.isDecelerating.x || this.isDecelerating.y || this.isBouncing.x || this.isBouncing.y) {
            this.isMomentumAnimating = true;//冲力动画进行时标志设为true
            this.momentumAnimationFramesHandled = 0;
            this.fireEvent('momentumanimationstart');//触发冲力动画开始事件

            me.handleMomentumAnimationFrame();
			
            this.momentumAnimationTimer = setInterval(function() {//设置动画无限定时器
                me.handleMomentumAnimationFrame();
            }, this.getFrameDuration());//获取动画延迟
            return true;
        }

        return false;
    },

    //结束冲力动画
    stopMomentumAnimation: function() {
		//如果冲力动画正在进行
        if (this.isMomentumAnimating) {
		
			//如果冲力动画计时器未清除
            if (this.momentumAnimationTimer) {
                clearInterval(this.momentumAnimationTimer);//清除冲力动画计时器
            }
            this.momentumAnimationEndTime = Date.now();//记录冲力动画结束时间

            var lastFps = this.getLastActualFps();//获取动画剩余量频率

			//如果没有设置最大频率，或剩余频率大于最大频率
            if (!this.maxFps || lastFps > this.maxFps) {
                this.maxFps = lastFps;//最大频率改为剩余频率
            }

			//如果自适应频率
            if (this.autoAdjustFps) {
                this.fps = this.maxFps;//频率为最大频率
            }

            this.isDecelerating = {};
            this.isBouncing = {};

            this.fireEvent('momentumanimationend');//触发冲力动画结束事件
            this.fireScrollEndEvent();

        }

        return this;
    },

    //动画每单位时间内运行的方法
    handleMomentumAnimationFrame : function() {
        if (!this.isMomentumAnimating) {//如果动画结束，直接跳出
            return;
        }

        var currentTime = Date.now(),//当前时间
            newOffset = this.offset.copy(),//新偏移量对象
            offsetBoundary = this.offsetBoundary,//拖拽范围
            currentVelocity,
            restrictedOffset,
            outOfBoundDistance;

        ['x', 'y'].forEach(function(axis) {
		
			//如果当前方向还可以做减速动画
            if (this.isDecelerating[axis]) {
                newOffset[axis] = this.decelerationAnimation[axis].getOffset();//当前动画的偏移坐标
                currentVelocity = this.momentumAnimationStartVelocity[axis] * this.decelerationAnimation[axis].getFrictionFactor();//获取摩擦系数
                outOfBoundDistance = offsetBoundary.getOutOfBoundOffset(axis, newOffset[axis]);

                
                if (outOfBoundDistance !== 0) {
                    restrictedOffset = offsetBoundary.restrict(axis, newOffset[axis]);

                    if (this.bounces && this.bounces[axis]) {
                        this.bouncingData[axis] = {
                            axis: axis,
                            offset: restrictedOffset,
                            time: currentTime,
                            velocity: currentVelocity
                        };

                        this.fireEvent('bouncestart', this, this.bouncingData[axis]);

                        this.bouncingAnimation[axis].set({
                            startTime: this.bouncingData[axis].time,
                            startOffset: this.bouncingData[axis].offset,
                            startVelocity: this.bouncingData[axis].velocity
                        });
                        this.isBouncing[axis] = true;
                    }

                    this.isDecelerating[axis] = false;
                }
                else if (Math.abs(currentVelocity) <= 1) {
                    this.isDecelerating[axis] = false;
                }
            }
            else if (this.isBouncing[axis]) {
                newOffset[axis] = this.bouncingAnimation[axis].getOffset();
                restrictedOffset = offsetBoundary.restrict(axis, newOffset[axis]);

                if (Math.abs(newOffset[axis] - restrictedOffset) <= 1) {
                    this.isBouncing[axis] = false;
                    this.fireEvent('bounceend', this, {axis: axis});
                    newOffset[axis] = restrictedOffset;
                }
            }
        }, this);

		//如果各方向已无减速动画或弹性动画可做
        if (!this.isDecelerating.x && !this.isDecelerating.y && !this.isBouncing.x && !this.isBouncing.y) {
            this.stopMomentumAnimation();//结束动画
            return;
        }

        this.momentumAnimationFramesHandled++;
        this.momentumAnimationProcessingTime += Date.now() - currentTime;//动画运行时间增加

        this.setOffset(newOffset);//滑到指定位置
    },

    
    destroy: function() {
        Ext.ScrollManager.unregister(this);
        return Ext.util.Scroller.superclass.destroy.apply(this, arguments);
    }
});

Ext.util.Scroller.Animation = {};

Ext.util.Scroller.Animation.Deceleration = Ext.extend(Ext.util.Draggable.Animation.Abstract, {
    acceleration: 30,
    theta: null,
    startVelocity: null,

    getOffset: function() {
        return this.startOffset - this.startVelocity * (1 - this.getFrictionFactor()) / this.theta;
    },

    getFrictionFactor : function() {
        var deltaTime = Date.now() - this.startTime;

        return Math.exp(deltaTime / this.acceleration * this.theta);
    }
});

Ext.util.Scroller.Animation.Bouncing = Ext.extend(Ext.util.Draggable.Animation.Abstract, {
    springTension: 0.3,
    acceleration: 30,
    startVelocity: null,

    getOffset: function() {
        var deltaTime = (Date.now() - this.startTime),
            powTime = (deltaTime / this.acceleration) * Math.pow(Math.E, -this.springTension * (deltaTime / this.acceleration));

        return this.startOffset + (this.startVelocity * powTime);
    }
});


Ext.util.Scroller.Indicator = Ext.extend(Object, {
    baseCls: 'x-scrollbar',

    ui: 'dark',

    
    type: 'horizontal',

    constructor: function(container, config) {
        this.container = container;

        Ext.apply(this, config);

        this.el = this.container.createChild({
            cls: [this.baseCls, this.baseCls + '-' + this.type, this.baseCls + '-' + this.ui].join(' ')
        });

        this.offset = new Ext.util.Offset();

        this.hide();
    },

    
    hide: function() {
        var me = this;

        if (this.hideTimer) {
            clearTimeout(this.hideTimer);
        }

        this.hideTimer = setTimeout(function() {
            me.el.setStyle('opacity', 0);
        }, 100);

        return this;
    },

    
    show: function() {
        if (this.hideTimer) {
            clearTimeout(this.hideTimer);
        }

        this.el.setStyle('opacity', 1);

        return this;
    },

    
    setVisibility: function(isVisible) {
        return this[isVisible ? 'show' : 'hide']();
    },

    
    setSize: function(size) {
        if (this.size && size > this.size) {
            size = Math.round(size);
        }

        
        this.el.dom.style[(this.type == 'horizontal') ? 'width' : 'height'] = size + 'px';

        this.size = size;

        return this;
    },

    //根据偏移量设置节点位置
    setOffset: function(offset) {
		//如果是纵向偏移
        if (this.type == 'vertical') {
            this.offset.y = offset;//对y坐标起作用
        } else {//如果是横向偏移
            this.offset.x = offset;//对x坐标起作用
        }

		//如果既不是ios设备，也不是pc
        if (!Ext.is.iOS && !Ext.is.Desktop) {
            if (this.type == 'vertical') {//如果是纵向偏移
                this.el.dom.style.top = this.offset.y + 'px';//改变top值
            } else {//如果是横向偏移
                this.el.dom.style.left = this.offset.x + 'px';//改变left值
            }
        }
		//否则使用css3控制
		else {
            Ext.Element.cssTranslate(this.el, this.offset);
        }

        return this;
    }

});

})();